package cn.xtits.xtf.common.spring.db.readwrite;

import org.apache.ibatis.executor.BatchResult;
import org.apache.ibatis.jdbc.RuntimeSqlException;
import org.apache.ibatis.session.Configuration;
import org.apache.ibatis.session.ResultHandler;
import org.apache.ibatis.session.RowBounds;
import org.apache.ibatis.session.SqlSession;
import org.mybatis.spring.SqlSessionTemplate;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.transaction.support.TransactionSynchronizationManager;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.sql.Connection;
import java.util.List;
import java.util.Map;


public class DynamicSqlSessionTemplate implements SqlSession {

    public static final Logger logger = LoggerFactory.getLogger(DynamicSqlSessionTemplate.class);

    public static final String PROXY_TYPE_JDK = "jdk";
    public static final String PROXY_TYPE_JAVA_SSIST = "java_ssist";

    public static final String DEFAULT_PROXY_TYPE = PROXY_TYPE_JDK;

    public static final String SELECT_PREFIX = "select";
    public static final String UPDATE_PREFIX = "update";
    public static final String DELETE_PREFIX = "delete";
    public static final String INSERT_PREFIX = "insert";

    private SqlSessionTemplate sqlSessionTemplate;

    private SqlSession sqlSessionProxy;

    public DynamicSqlSessionTemplate(SqlSessionTemplate sqlSessionTemplate) {
        this.sqlSessionTemplate = sqlSessionTemplate;
        this.sqlSessionProxy = (SqlSession) Proxy.newProxyInstance(SqlSession.class.getClassLoader(),
                                                                   new Class[] { SqlSession.class },
                                                                   new SqlSessionInterceptor());
    }

    private class SqlSessionInterceptor implements InvocationHandler {

        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            try {
                boolean actualTransactionActive = TransactionSynchronizationManager.isActualTransactionActive();
                //TODO 如果在spring事务中
                if (actualTransactionActive) {
                    return method.invoke(sqlSessionTemplate, args);
                } else {
                    //TODO 如果不在spring事务中,select分发到slave库, [update,insert,delete]分发到主库
                    String methodName = method.getName();
                    if (methodName.startsWith(SELECT_PREFIX)) {
                        DynamicReadWriterDataSourceHolder.setSlave();
                    } else if (methodName.startsWith(UPDATE_PREFIX) ||
                               methodName.startsWith(DELETE_PREFIX) ||
                               methodName.startsWith(INSERT_PREFIX)) {
                        DynamicReadWriterDataSourceHolder.setMaster();
                    } else {
                        throw new RuntimeSqlException("不支持此方法名!");
                    }

                    Object result = method.invoke(sqlSessionTemplate, args);
                    DynamicReadWriterDataSourceHolder.clear();
                    return result;
                }
            } catch (Exception e) {
                throw e;
            }
        }
    }

    public <T> T selectOne(String statement) {
        return sqlSessionProxy.selectOne(statement);
    }

    public <T> T selectOne(String statement, Object parameter) {
        return sqlSessionProxy.selectOne(statement, parameter);
    }

    public <E> List<E> selectList(String statement) {
        return sqlSessionProxy.selectList(statement);
    }

    public <E> List<E> selectList(String statement, Object parameter) {
        return sqlSessionProxy.selectList(statement, parameter);
    }

    public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {
        return sqlSessionProxy.selectList(statement, parameter, rowBounds);
    }

    public <K, V> Map<K, V> selectMap(String statement, String mapKey) {
        return sqlSessionProxy.selectMap(statement, mapKey);
    }

    public <K, V> Map<K, V> selectMap(String statement, Object parameter, String mapKey) {
        return sqlSessionProxy.selectMap(statement, parameter, mapKey);
    }

    public <K, V> Map<K, V> selectMap(String statement, Object parameter, String mapKey, RowBounds rowBounds) {
        return sqlSessionProxy.selectMap(statement, parameter, mapKey, rowBounds);
    }

    public void select(String statement, Object parameter, ResultHandler handler) {
        sqlSessionProxy.select(statement, parameter, handler);
    }

    public void select(String statement, ResultHandler handler) {
        sqlSessionProxy.select(statement, handler);
    }

    public void select(String statement, Object parameter, RowBounds rowBounds, ResultHandler handler) {
        sqlSessionProxy.select(statement, parameter, rowBounds, handler);
    }

    public int insert(String statement) {
        return sqlSessionProxy.insert(statement);
    }

    public int insert(String statement, Object parameter) {
        return sqlSessionProxy.insert(statement, parameter);
    }

    public int update(String statement) {
        return sqlSessionProxy.update(statement);
    }

    public int update(String statement, Object parameter) {
        return sqlSessionProxy.update(statement, parameter);
    }

    public int delete(String statement) {
        return sqlSessionProxy.delete(statement);
    }

    public int delete(String statement, Object parameter) {
        return sqlSessionProxy.delete(statement, parameter);
    }

    public void commit() {
        sqlSessionProxy.commit();
    }

    public void commit(boolean force) {
        sqlSessionProxy.commit(force);
    }

    public void rollback() {
        sqlSessionProxy.rollback();
    }

    public void rollback(boolean force) {
        sqlSessionProxy.rollback(force);
    }

    public List<BatchResult> flushStatements() {
        return sqlSessionProxy.flushStatements();
    }

    public void close() {
        sqlSessionProxy.close();
    }

    public void clearCache() {
        sqlSessionProxy.clearCache();
    }

    public Configuration getConfiguration() {
        return sqlSessionProxy.getConfiguration();
    }

    public <T> T getMapper(Class<T> type) {
        return sqlSessionProxy.getMapper(type);
    }

    public Connection getConnection() {
        return sqlSessionProxy.getConnection();
    }
}
